﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SailHero.Soft.ESB.Client
{
    public class CommunicationCommon
    {
        /// <summary>
        /// A flag to control thread's running
        /// </summary>
        public volatile bool _running;

        public static ManualResetEvent posReceiveDone = new ManualResetEvent(false);

        /// <summary>
        /// Socket object to send/reveice messages.
        /// </summary>
        public Socket ClientSocket
        {
            get;
            set;
        }

        /// <summary>
        /// 是否进行CRC校验 0 不校验 1校验
        /// </summary>
        public static string IsCRCCheck = "0";//

        /// <summary>
        /// CRC校验的类型 0 万维的，1 HJT标准的
        /// </summary>
        public static string CrcCheckType = "0"; 


        public CommunicationCommon()
        {
            _buffer = new byte[ReceiveBufferSize];
            _receiveMemoryStream = new MemoryStream();
        }

        private MyProtocol myProtocol = new MyProtocol();
        /// <summary>
        /// This buffer is used to receive bytes 
        /// </summary>
        public readonly byte[] _buffer;
        /// <summary>
        /// 消息接收事件
        /// </summary>
        public event EventHandler<MessageEventArgs> MessageReceived;

        /// <summary>
        /// This MemoryStream object is used to collect receiving bytes to build messages.
        /// </summary>
        private MemoryStream _receiveMemoryStream;

        /// <summary>
        /// Maximum length of a message.
        /// </summary>
        private const int MaxMessageLength = 128 * 1024 * 1024; //128 Megabytes.

        /// <summary>
        /// Size of the buffer that is used to receive bytes from TCP socket.
        /// </summary>
        private const int ReceiveBufferSize = 2 * 1024; //2KB 此处比协议规定的要稍微大点

        /// <summary>
        /// Gets the time of the last succesfully received message.
        /// </summary>
        public DateTime LastReceivedMessageTime { get; protected set; }
        /// <summary>
        /// This method is used as callback method in _clientSocket's BeginReceive method.
        /// It reveives bytes from socker.
        /// </summary>
        /// <param name="ar">Asyncronous call result</param>
        public void ReceiveCallback(IAsyncResult ar)
        {
            if (!_running)
            {
                return;
            }

            try
            {

                //Get received bytes count
                var bytesRead = ClientSocket.EndReceive(ar);
                if (bytesRead > 0)
                {
                    LastReceivedMessageTime = DateTime.Now;
                    //Copy received bytes to a new byte array
                    var receivedBytes = new byte[bytesRead];
                    Array.Copy(_buffer, 0, receivedBytes, 0, bytesRead);
                    //Read messages according to current wire protocol
                    var messages = CreateMessages(receivedBytes);
                    //Raise MessageReceived event for all received messages
                    foreach (var message in messages)
                    {
                        OnMessageReceived(message);
                    }
                }
                else
                {
                    Errorlog.Write(new Exception("Tcp socket is closed"));
                }
                //Read more bytes if still running
                if (_running)
                {
                    ClientSocket.BeginReceive(_buffer, 0, _buffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
                }
            }
            catch (Exception ex)
            {
                Errorlog.Write(ex);
                Disconnect();
                posReceiveDone.Set();
            }
        }


        /// <summary>
        /// Disconnects from remote application and closes channel.
        /// </summary>
        public void Disconnect()
        {

            _running = false;
            try
            {
                if (ClientSocket.Connected)
                {
                    ClientSocket.Close();
                }
            }
            catch (Exception ex)
            {
                Errorlog.Write(ex);
            }
        }

        /// <summary>
        /// Builds messages from a byte array that is received from remote application.
        /// The Byte array may contain just a part of a message, the protocol must
        /// cumulate bytes to build messages.
        /// This method is synchronized. So, only one thread can call it concurrently.
        /// </summary>
        /// <param name="receivedBytes">Received bytes from remote application</param>
        /// <returns>
        /// List of messages.
        /// Protocol can generate more than one message from a byte array.
        /// Also, if received bytes are not sufficient to build a message, the protocol
        /// may return an empty list (and save bytes to combine with next method call).
        /// </returns>
        public IEnumerable<Message> CreateMessages(byte[] receivedBytes)
        {
            //Write all received bytes to the _receiveMemoryStream
            _receiveMemoryStream.Write(receivedBytes, 0, receivedBytes.Length);
            //Create a list to collect messages
            var messages = new List<Message>();
            //Read all available messages and add to messages collection
            while (ReadSingleMessage(messages)) { }
            //Return message list
            return messages;
        }

        /// <summary>
        /// Serializes a message to a byte array to send to remote application.
        /// This method is synchronized. So, only one thread can call it concurrently.
        /// </summary>
        /// <param name="message">Message to be serialized</param>
        /// <exception cref="CommunicationException">Throws CommunicationException if message is bigger than maximum allowed message length.</exception>
        public byte[] GetBytes(Message message)
        {
            //Serialize the message to a byte array
            var serializedMessage = myProtocol.SerializeMessage(message);

            //Check for message length
            var messageLength = serializedMessage.Length;
            if (messageLength > MaxMessageLength)
            {
                Errorlog.Write(new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MaxMessageLength + " bytes."));
            }

            //Create a byte array including the length of the message (4 bytes) and serialized message content
            //var bytes = new byte[messageLength + 4];
            //WriteInt32(bytes, 0, messageLength);
            //Array.Copy(serializedMessage, 0, bytes, 4, messageLength);

            return serializedMessage;
        }
        /// <summary>
        /// This method tries to read a single message and add to the messages collection. 
        /// </summary>
        /// <param name="messages">Messages collection to collect messages</param>
        /// <returns>
        /// Returns a boolean value indicates that if there is a need to re-call this method.
        /// </returns>
        /// <exception cref="CommunicationException">Throws CommunicationException if message is bigger than maximum allowed message length.</exception>
        private bool ReadSingleMessage(ICollection<Message> messages)
        {
            //Go to the begining of the stream
            _receiveMemoryStream.Position = 0;

            ///todo:需要在此处处理非.net客户端发送消息的问题

            //If stream has less than 6 bytes, that means we can not even read length of the message
            //So, return false to wait more bytes from remore application.
            if (_receiveMemoryStream.Length < 6)//消息长度小于6 时会自动抛弃消息
            {
                return false;
            }

            //Read length of the message
            int messageLength = 0;
            var Mbuffer = ReadByteArray(_receiveMemoryStream, 6);
            using (MemoryStream msr = new MemoryStream(Mbuffer))
            {
                BinaryReader reader = new BinaryReader(msr);
                byte[] _heads = reader.ReadBytes(2);
                string heads = System.Text.Encoding.ASCII.GetString(_heads);
                //判断包头
                if (heads != "##")
                {
                    _receiveMemoryStream = new MemoryStream();//不合法数据自动丢弃
                    return false;
                }
                //读取内容的长度
                messageLength = Convert.ToInt32(System.Text.Encoding.ASCII.GetString(reader.ReadBytes(4)));
                reader.Close();
            }
            _receiveMemoryStream.Position = 0;
            if (messageLength > MaxMessageLength)
            {
                throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MaxMessageLength + " bytes.");
            }

            //If message is zero-length (It must not be but good approach to check it)
            if (messageLength == 0)
            {
                //if no more bytes, return immediately
                if (_receiveMemoryStream.Length == 12)
                {
                    _receiveMemoryStream = new MemoryStream(); //Clear the stream
                    return false;
                }

                //Create a new memory stream from current except first 4-bytes.
                //var bytes = _receiveMemoryStream.ToArray();
                //_receiveMemoryStream = new MemoryStream();
                //_receiveMemoryStream.Write(bytes, 4, bytes.Length - 4);
                return true;
            }

            //If all bytes of the message is not received yet, return to wait more bytes
            if (_receiveMemoryStream.Length < messageLength)
            {
                _receiveMemoryStream.Position = _receiveMemoryStream.Length;
                return false;
            }

            //Read bytes of serialized message and deserialize it
            var serializedMessageBytes = ReadByteArray(_receiveMemoryStream, messageLength + 12);
            messages.Add(myProtocol.DeserializeMessage(serializedMessageBytes));

            //Read remaining bytes to an array
            var remainingBytes = ReadByteArray(_receiveMemoryStream, (int)(_receiveMemoryStream.Length - messageLength - 12));

            //Re-create the receive memory stream and write remaining bytes
            _receiveMemoryStream = new MemoryStream();
            _receiveMemoryStream.Write(remainingBytes, 0, remainingBytes.Length);
            //Return true to re-call this method to try to read next message
            return (remainingBytes.Length > 12);
        }
        /// <summary>
        /// Reads a byte array with specified length.
        /// </summary>
        /// <param name="stream">Stream to read from</param>
        /// <param name="length">Length of the byte array to read</param>
        /// <returns>Read byte array</returns>
        /// <exception cref="EndOfStreamException">Throws EndOfStreamException if can not read from stream.</exception>
        private static byte[] ReadByteArray(Stream stream, int length)
        {
            var buffer = new byte[length];
            var totalRead = 0;
            while (totalRead < length)
            {
                var read = stream.Read(buffer, totalRead, length - totalRead);
                if (read <= 0)
                {
                    Errorlog.Write(new EndOfStreamException("Can not read from stream! Input stream is closed."));
                }

                totalRead += read;
            }

            return buffer;
        }


        /// <summary>
        /// Raises MessageReceived event.
        /// </summary>
        /// <param name="message">Received message</param>
        protected virtual void OnMessageReceived(Message message)
        {
            var handler = MessageReceived;
            if (handler != null)
            {
                handler(this, new MessageEventArgs(message));
            }
        }



    }
}
